package database

import (
	"context"

	"github.com/pkg/errors"
	"gorm.io/gorm"
)

// Sql ...
type Sql struct{}

// Create ...
func (Sql) Create(db *gorm.DB, m interface{}) error {
	err := db.Create(m).Error
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// CreateInBatches ...
func (Sql) CreateInBatches(db *gorm.DB, ms interface{}, size int) error {
	err := db.CreateInBatches(ms, size).Error
	return errors.WithStack(err)
}

// Save ...
func (Sql) Save(db *gorm.DB, m interface{}) error {
	err := db.Save(m).Error
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// First ...
func (Sql) First(db *gorm.DB, m interface{}, filter ...interface{}) error {
	err := db.First(m, filter...).Error
	if err == gorm.ErrRecordNotFound {
		return err
	}
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// Pluck ...
func (Sql) Pluck(db *gorm.DB, m interface{}, c string, v interface{}, filter ...interface{}) error {
	if len(filter) > 0 {
		db = db.Where(filter[0], filter[1:]...)
	}
	err := db.Model(m).Pluck(c, v).Error
	if err == gorm.ErrRecordNotFound {
		return err
	}
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// Update ...
func (Sql) Update(db *gorm.DB, m interface{}) error {
	type attributer interface {
		Attributes() map[string]interface{}
		Refresh()
	}
	var err error
	if attr, ok := m.(attributer); ok {
		attributes := attr.Attributes()
		if len(attributes) == 0 {
			return nil
		}
		err = db.Model(m).Updates(attributes).Error
		if err != nil {
			return errors.WithStack(err)
		}
		attr.Refresh()
	} else {
		err = db.Updates(m).Error
		if err != nil {
			return errors.WithStack(err)
		}
	}

	return nil
}

// Count ...
func (Sql) Count(db *gorm.DB, model interface{}, v *int64, filter ...interface{}) error {
	if len(filter) > 0 {
		db = db.Where(filter[0], filter[1:]...)
	}
	err := db.Model(model).Count(v).Error
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// Delete ...
func (Sql) Delete(db *gorm.DB, m interface{}, filter ...interface{}) error {
	err := db.Delete(m, filter...).Error
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// Find ...
func (Sql) Find(db *gorm.DB, m interface{}, filter ...interface{}) error {
	err := db.Find(m, filter...).Error
	if err == gorm.ErrRecordNotFound {
		return err
	}
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}

// Base ...
type Base struct {
	Sql
}

// CreateInBatches ...
func (r *Base) CreateInBatches(ctx context.Context, ms interface{}, size int) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.CreateInBatches(db, ms, size)
}

// Create ...
func (r *Base) Create(ctx context.Context, m interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.Create(db, m)
}

// Save ...
func (r *Base) Save(ctx context.Context, m interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.Save(db, m)
}

// First ...
func (r *Base) First(ctx context.Context, m interface{}, filter ...interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.First(db, m, filter...)
}

// Pluck ...
func (r *Base) Pluck(ctx context.Context, m interface{}, c string, v interface{}, filter ...interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.Pluck(db, m, c, v, filter...)
}

// Count ...
func (r *Base) Count(ctx context.Context, model interface{}, v *int64, filter ...interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.Count(db, model, v, filter...)
}

// Update ...
func (r *Base) Update(ctx context.Context, m interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}

	return r.Sql.Update(db, m)
}

// Delete ...
func (r *Base) Delete(ctx context.Context, m interface{}, filter ...interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}
	return r.Sql.Delete(db, m, filter...)
}

// Find ...
func (r *Base) Find(ctx context.Context, m interface{}, filter ...interface{}) error {
	db := Instance(ctx)
	if db == nil {
		return errors.New("Sql object is nil")
	}
	return r.Sql.Find(db, m, filter...)
}
